#define NOMINMAX
#include <string>
#include <list>
#include <iostream>
#include <filesystem>
#include <fstream>
#include <windows.h>

#include "include/cef_app.h"
#include "include/cef_sandbox_win.h"
#include "include/cef_browser.h"
#include "include/cef_command_line.h"
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
#include "include/wrapper/cef_helpers.h"
#include "include/base/cef_callback.h"
#include "include/cef_parser.h"
#include "include/wrapper/cef_closure_task.h"


#include "include/cef_app.h"
#include "include/cef_client.h"
#include "include/cef_load_handler.h"
#include "include/cef_life_span_handler.h"
#include "include/cef_render_handler.h"

#pragma comment(lib, "./libcef.lib")
#pragma comment(lib, "./libcef_dll_wrapper.lib")

//using namespace std::literals;

class SimpleHandler;

namespace {

    SimpleHandler* g_instance = nullptr;

    // Returns a data: URI with the specified contents.
    std::string GetDataURI(const std::string& data, const std::string& mime_type) {
        return "data:" + mime_type + ";base64," +
            CefURIEncode(CefBase64Encode(data.data(), data.size()), false)
            .ToString();
    }

}

namespace {

    // When using the Views framework this object provides the delegate
    // implementation for the CefWindow that hosts the Views-based browser.
    class SimpleWindowDelegate : public CefWindowDelegate {
    public:
        explicit SimpleWindowDelegate(CefRefPtr<CefBrowserView> browser_view)
            : browser_view_(browser_view) {
        }

        void OnWindowCreated(CefRefPtr<CefWindow> window) override {
            // Add the browser view and show the window.
            window->AddChildView(browser_view_);
            window->Show();

            // Give keyboard focus to the browser view.
            browser_view_->RequestFocus();
        }

        void OnWindowDestroyed(CefRefPtr<CefWindow> window) override {
            browser_view_ = nullptr;
        }

        bool CanClose(CefRefPtr<CefWindow> window) override {
            // Allow the window to close if the browser says it's OK.
            CefRefPtr<CefBrowser> browser = browser_view_->GetBrowser();
            if (browser)
                return browser->GetHost()->TryCloseBrowser();
            return true;
        }

        CefSize GetPreferredSize(CefRefPtr<CefView> view) override {
            return CefSize(800, 600);
        }

    private:
        CefRefPtr<CefBrowserView> browser_view_;

        IMPLEMENT_REFCOUNTING(SimpleWindowDelegate);
        DISALLOW_COPY_AND_ASSIGN(SimpleWindowDelegate);
    };

    class SimpleBrowserViewDelegate : public CefBrowserViewDelegate {
    public:
        SimpleBrowserViewDelegate() {}

        bool OnPopupBrowserViewCreated(CefRefPtr<CefBrowserView> browser_view,
            CefRefPtr<CefBrowserView> popup_browser_view,
            bool is_devtools) override {
            // Create a new top-level Window for the popup. It will show itself after
            // creation.
            CefWindow::CreateTopLevelWindow(
                new SimpleWindowDelegate(popup_browser_view));

            // We created the Window.
            return true;
        }

    private:
        IMPLEMENT_REFCOUNTING(SimpleBrowserViewDelegate);
        DISALLOW_COPY_AND_ASSIGN(SimpleBrowserViewDelegate);
    };

}

class SimpleHandler : public CefClient,
    public CefDisplayHandler,
    public CefLifeSpanHandler,
    public CefLoadHandler {
public:
    explicit SimpleHandler(bool use_views) : use_views_(use_views), is_closing_(false) {
        DCHECK(!g_instance);
        g_instance = this;
    }
    ~SimpleHandler() {
        g_instance = nullptr;
    }

    // Provide access to the single global instance of this object.
    static SimpleHandler* GetInstance() {
        return g_instance;
    }

    // CefClient methods:
    virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() override {
        return this;
    }
    virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override {
        return this;
    }
    virtual CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }

    // CefDisplayHandler methods:
    virtual void OnTitleChange(CefRefPtr<CefBrowser> browser,
        const CefString& title) override {
        CEF_REQUIRE_UI_THREAD();

        if (use_views_) {
            // Set the title of the window using the Views framework.
            CefRefPtr<CefBrowserView> browser_view =
                CefBrowserView::GetForBrowser(browser);
            if (browser_view) {
                CefRefPtr<CefWindow> window = browser_view->GetWindow();
                if (window)
                    window->SetTitle(title);
            }
        }
        else if (!IsChromeRuntimeEnabled()) {
            // Set the title of the window using platform APIs.
            PlatformTitleChange(browser, title);
        }
    }

    // CefLifeSpanHandler methods:
    virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
        CEF_REQUIRE_UI_THREAD();

        // Add to the list of existing browsers.
        browser_list_.push_back(browser);
    }
    virtual bool DoClose(CefRefPtr<CefBrowser> browser) override {
        CEF_REQUIRE_UI_THREAD();

        // Closing the main window requires special handling. See the DoClose()
        // documentation in the CEF header for a detailed destription of this
        // process.
        if (browser_list_.size() == 1) {
            // Set a flag to indicate that the window close should be allowed.
            is_closing_ = true;
        }

        // Allow the close. For windowed browsers this will result in the OS close
        // event being sent.
        return false;
    }
    virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
        CEF_REQUIRE_UI_THREAD();

        // Remove from the list of existing browsers.
        BrowserList::iterator bit = browser_list_.begin();
        for (; bit != browser_list_.end(); ++bit) {
            if ((*bit)->IsSame(browser)) {
                browser_list_.erase(bit);
                break;
            }
        }

        if (browser_list_.empty()) {
            // All browser windows have closed. Quit the application message loop.
            CefQuitMessageLoop();
        }
    }

    // CefLoadHandler methods:
    virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        ErrorCode errorCode,
        const CefString& errorText,
        const CefString& failedUrl) override {
        CEF_REQUIRE_UI_THREAD();

        // Allow Chrome to show the error page.
        if (IsChromeRuntimeEnabled())
            return;

        // Don't display an error for downloaded files.
        if (errorCode == ERR_ABORTED)
            return;

        // Display a load error message using a data: URI.
        std::stringstream ss;
        ss << "<html><body bgcolor=\"white\">"
            "<h2>Failed to load URL "
            << std::string(failedUrl) << " with error " << std::string(errorText)
            << " (" << errorCode << ").</h2></body></html>";

        frame->LoadURL(GetDataURI(ss.str(), "text/html"));
    }

    // Request that all existing browser windows close.
    void CloseAllBrowsers(bool force_close) {
        if (!CefCurrentlyOn(TID_UI)) {
            // Execute on the UI thread.
            CefPostTask(TID_UI, base::BindOnce(&SimpleHandler::CloseAllBrowsers, this,
                force_close));
            return;
        }

        if (browser_list_.empty())
            return;

        BrowserList::const_iterator it = browser_list_.begin();
        for (; it != browser_list_.end(); ++it)
            (*it)->GetHost()->CloseBrowser(force_close);
    }

    bool IsClosing() const { return is_closing_; }

    // Returns true if the Chrome runtime is enabled.
    static bool IsChromeRuntimeEnabled() {
        static int value = -1;
        if (value == -1) {
            CefRefPtr<CefCommandLine> command_line =
                CefCommandLine::GetGlobalCommandLine();
            value = command_line->HasSwitch("enable-chrome-runtime") ? 1 : 0;
        }
        return value == 1;
    }

private:
    // Platform-specific implementation.
    void PlatformTitleChange(CefRefPtr<CefBrowser> browser,
        const CefString& title) {
        CefWindowHandle hwnd = browser->GetHost()->GetWindowHandle();
        if (hwnd)
            SetWindowText(hwnd, std::wstring(title).c_str());
    }

    // True if the application is using the Views framework.
    const bool use_views_;

    // List of existing browser windows. Only accessed on the CEF UI thread.
    typedef std::list<CefRefPtr<CefBrowser>> BrowserList;
    BrowserList browser_list_;

    bool is_closing_;

    // Include the default reference counting implementation.
    IMPLEMENT_REFCOUNTING(SimpleHandler);
};

// Implement application-level callbacks for the browser process.
class SimpleApp : public CefApp, public CefBrowserProcessHandler {
public:
    SimpleApp() {}

    // CefApp methods:
    CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override {
        return this;
    }

    // CefBrowserProcessHandler methods:
    void OnContextInitialized() override {
        CEF_REQUIRE_UI_THREAD();

        CefRefPtr<CefCommandLine> command_line =
            CefCommandLine::GetGlobalCommandLine();

        // Create the browser using the Views framework if "--use-views" is specified
        // via the command-line. Otherwise, create the browser using the native
        // platform framework.
        const bool use_views = command_line->HasSwitch("use-views");

        // SimpleHandler implements browser-level callbacks.
        CefRefPtr<SimpleHandler> handler(new SimpleHandler(use_views));

        // Specify CEF browser settings here.
        CefBrowserSettings browser_settings;

        std::string url;

        // Check if a "--url=" value was provided via the command-line. If so, use
        // that instead of the default URL.
        url = command_line->GetSwitchValue("url");
        if (url.empty())
            url = "http://www.baidu.com";

        if (use_views) {
            // Create the BrowserView.
            CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
                handler, url, browser_settings, nullptr, nullptr,
                new SimpleBrowserViewDelegate());

            // Create the Window. It will show itself after creation.
            CefWindow::CreateTopLevelWindow(new SimpleWindowDelegate(browser_view));
        }
        else {
            // Information used when creating the native window.
            CefWindowInfo window_info;

#if defined(OS_WIN)
            // On Windows we need to specify certain flags that will be passed to
            // CreateWindowEx().
            window_info.SetAsPopup(nullptr, "cefsimple");
#endif

            // Create the first browser window.
            CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,
                nullptr, nullptr);
        }
    }
    CefRefPtr<CefClient> GetDefaultClient() override {
        // Called when a new browser window is created via the Chrome runtime UI.
        return SimpleHandler::GetInstance();
    }

private:
    // Include the default reference counting implementation.
    IMPLEMENT_REFCOUNTING(SimpleApp);
};

class NanoCefApp : public CefApp
{
    IMPLEMENT_REFCOUNTING(NanoCefApp);
};

class NanoCefClient : public CefClient, public CefLifeSpanHandler//, public CefLoadHandler
{
public:
    CefRefPtr<CefBrowser> GetBrowser() { return pBrowser_; }
    CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
    //CefRefPtr<CefRenderHandler> GetRenderHandler() override { return this; }
    //CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }

    void OnAfterCreated(CefRefPtr<CefBrowser> pBrowser) override { pBrowser_ = pBrowser; }

private:
    CefRefPtr<CefBrowser> pBrowser_;
    IMPLEMENT_REFCOUNTING(CefClient);
};

CefRefPtr<NanoCefClient> pClient;

COLORREF transparentColor = RGB(255, 255, 255);
LRESULT CALLBACK BrowserWindowWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CREATE:
    {
        //SetLayeredWindowAttributes(hWnd, transparentColor, 255, LWA_COLORKEY | LWA_ALPHA);

        pClient = new NanoCefClient();
        RECT rect{};
        GetClientRect(hWnd, &rect);
        CefRect cefRect;
        cefRect.x = rect.left;
        cefRect.y = rect.top;
        cefRect.width = rect.right - rect.left;
        cefRect.height = rect.bottom - rect.top;

        CefWindowInfo info;
        info.SetAsChild(hWnd, cefRect);
        //info.SetAsWindowless(hWnd);

        CefBrowserSettings browser_settings;
        CefBrowserHost::CreateBrowser(info, pClient, "https://www.qq.com", {}, {}, {});
        break;
    }
    case WM_LBUTTONDOWN:
    {
        PostMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, lParam);
        break;
    }
    case WM_SIZE:
    {
        if (wParam != SIZE_MINIMIZED && pClient)
        {
            if (auto pBrowser = pClient->GetBrowser())
            {
                if (auto hWndBrowser = pBrowser->GetHost()->GetWindowHandle())
                {
                    RECT rect{};
                    GetClientRect(hWnd, &rect);
                    SetWindowPos(hWndBrowser, NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
                }
            }
        }
        break;
    }
    case WM_ERASEBKGND:
    {
        if (pClient)
        {
            if (auto pBrowser = pClient->GetBrowser())
            {
                if (pBrowser->GetHost()->GetWindowHandle())
                {
                    return 1;
                }
            }
        }
        break;
    }
    case WM_DESTROY:
    {
        CefShutdown();
        PostQuitMessage(0);
        break;
    }
    default:
        break;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

#if defined(CEF_USE_SANDBOX)
// The cef_sandbox.lib static library may not link successfully with all VS
// versions.
#pragma comment(lib, "cef_sandbox.lib")
#endif

int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow) {
    UNREFERENCED_PARAMETER(hInstPrev);
    UNREFERENCED_PARAMETER(cmdline);
    /**
    const CefMainArgs mainAtrgs{ hInst };
    CefRefPtr<NanoCefApp> pApp = new NanoCefApp();
    if (const auto code = CefExecuteProcess(mainAtrgs, pApp, nullptr); code >= 0) {
        return (int)code;
    }

    CefSettings settings;
    settings.multi_threaded_message_loop = true;
    //settings.windowless_rendering_enabled = true;
    CefString(&settings.cache_path).FromString((std::filesystem::current_path() / "cef_cache").string());
    CefInitialize(mainAtrgs, settings, pApp, nullptr);

    LPCTSTR wndClassName = TEXT("$browser-window$");
    HWND hWndBrowser = nullptr;
    {
        WNDCLASSEX wcex{};
        wcex.cbSize = sizeof(wcex);
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = BrowserWindowWndProc;
        wcex.hInstance = hInst;
        wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
        wcex.hbrBackground = CreateSolidBrush(transparentColor);
        wcex.lpszClassName = wndClassName;
        RegisterClassEx(&wcex);

        hWndBrowser = CreateWindowEx(
            0, wndClassName, L"Nano CEF",
            WS_POPUP | WS_CLIPCHILDREN, 200, 20,
            800, 600, nullptr, nullptr, hInst, nullptr
        );
        ShowWindow(hWndBrowser, SW_SHOWDEFAULT);
        UpdateWindow(hWndBrowser);
    }

    MSG msg;
    while (GetMessageA(&msg, nullptr, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessageA(&msg);
        //CefDoMessageLoopWork();
    }

    pClient.reset();
    UnregisterClass(wndClassName, hInst);

    return (int)msg.wParam;
    */

    // Enable High-DPI support on Windows 7 or newer.
    CefEnableHighDPISupport();

    void* sandbox_info = nullptr;

#if defined(CEF_USE_SANDBOX)
    // Manage the life span of the sandbox information object. This is necessary
    // for sandbox support on Windows. See cef_sandbox_win.h for complete details.
    CefScopedSandboxInfo scoped_sandbox;
    sandbox_info = scoped_sandbox.sandbox_info();
#endif

    // Provide CEF with command-line arguments.
    CefMainArgs main_args(hInst);

    // CEF applications have multiple sub-processes (render, GPU, etc) that share
    // the same executable. This function checks the command-line and, if this is
    // a sub-process, executes the appropriate logic.
    int exit_code = CefExecuteProcess(main_args, nullptr, sandbox_info);
    if (exit_code >= 0) {
        // The sub-process has completed so return here.
        return exit_code;
    }

    // Parse command-line arguments for use in this method.
    CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
    command_line->InitFromString(::GetCommandLineW());

    // Specify CEF global settings here.
    CefSettings settings;

    if (command_line->HasSwitch("enable-chrome-runtime")) {
        // Enable experimental Chrome runtime. See issue #2969 for details.
        settings.chrome_runtime = true;
    }

#if !defined(CEF_USE_SANDBOX)
    settings.no_sandbox = true;
#endif

    // SimpleApp implements application-level callbacks for the browser process.
    // It will create the first browser instance in OnContextInitialized() after
    // CEF has initialized.
    CefRefPtr<SimpleApp> app(new SimpleApp);

    // Initialize CEF.
    CefInitialize(main_args, settings, app.get(), sandbox_info);

    // Run the CEF message loop. This will block until CefQuitMessageLoop() is
    // called.
    CefRunMessageLoop();

    // Shut down CEF.
    CefShutdown();
    return 0;
}

